#ifdef USE_IP175
//
// Created by gastonfeng on 2023/11/11.
//

#include "IP175.h"
#include "logger_rte.h"
#include "core_debug.h"

void IP175::GPIO_SET_MDIO_MODE_INPUT() {
    // Set MIDO pin to it's default status with HAL_GPIO_DeInit
    pinMode(mdio, INPUT);
    pinMode(mdc, OUTPUT);
}

void IP175::GPIO_SET_MDIO_MDC_MODE_INPUT() {
    pinMode(mdio, INPUT);
    pinMode(mdc, INPUT);
}

void IP175::_MIIM_DRIVER_CLOCK_PULSE() {
    digitalWrite(mdc, LOW);
    HAL_Delay(2);
    digitalWrite(mdc, HIGH);
    HAL_Delay(2);
}

void IP175::_MIIM_DRIVER_START() {
    // Preamble
    // HAL_GPIO_WritePin(GPIOA, MIIM_MDIO_Pin, GPIO_PIN_SET);

    for (uint8_t i = 0; i < 5; ++i) {
        _MIIM_DRIVER_CLOCK_PULSE();
    }

    // Start clock
    digitalWrite(mdio, LOW);
    _MIIM_DRIVER_CLOCK_PULSE();
    digitalWrite(mdio, HIGH);
    _MIIM_DRIVER_CLOCK_PULSE();
}

void IP175::_MIIM_DRIVER_OP_CODE_READ() {
    // Data = 0b10
    digitalWrite(mdio, HIGH);
    _MIIM_DRIVER_CLOCK_PULSE();
    digitalWrite(mdio, LOW);
    _MIIM_DRIVER_CLOCK_PULSE();
}

void IP175::_MIIM_DRIVER_OP_CODE_WRITE() {
    // Data = 0b01
    digitalWrite(mdio, LOW);
    _MIIM_DRIVER_CLOCK_PULSE();
    digitalWrite(mdio, HIGH);
    _MIIM_DRIVER_CLOCK_PULSE();
}

void IP175::_MIIM_DRIVER_PHY_REG_OUTPUT(uint8_t PHY, uint8_t REG) {
    // Send the PHY address first
    for (uint8_t bitnum = 0; bitnum <= 4; ++bitnum) {
        if ((PHY & (1 << (4 - bitnum))) == 0) {
            digitalWrite(mdio, LOW);
        } else {
            digitalWrite(mdio, HIGH);
        }
        _MIIM_DRIVER_CLOCK_PULSE();
    }

    // Send the REG address first
    for (uint8_t bitnum = 0; bitnum <= 4; ++bitnum) {
        if ((REG & (1 << (4 - bitnum))) == 0) {
            digitalWrite(mdio, LOW);
        } else {
            digitalWrite(mdio, HIGH);
        }
        _MIIM_DRIVER_CLOCK_PULSE();
    }
}

void IP175::_MIIM_DRIVER_TA_WRITE() {
    // The Turnaround bits for write are the same as the op code
    _MIIM_DRIVER_OP_CODE_READ();
}

void IP175::_MIIM_DRIVER_TA_READ() {
    // The TA bits for read
    GPIO_SET_MDIO_MODE_INPUT();
    _MIIM_DRIVER_CLOCK_PULSE();
    pinMode(mdc, OUTPUT);
    pinMode(mdio, OUTPUT);
    digitalWrite(mdio, LOW);
    digitalWrite(mdc, LOW);
    _MIIM_DRIVER_CLOCK_PULSE();
}

void IP175::_MIIM_DRIVER_WRITE_DATA(uint16_t data) {
    for (uint8_t bitnum = 0; bitnum <= 15; ++bitnum) {
        if ((data & (1 << (15 - bitnum))) == 0) {
            digitalWrite(mdio, LOW);
        } else {
            digitalWrite(mdio, HIGH);
        }
        _MIIM_DRIVER_CLOCK_PULSE();
    }
    // reset clock and data
    digitalWrite(mdio, HIGH);
    digitalWrite(mdc, LOW);
    // final clock pulse afterwards?
}

uint16_t IP175::_MIIM_DRIVER_READ_DATA() {
    GPIO_SET_MDIO_MODE_INPUT();
    uint16_t data = 0;
    for (uint8_t bitnum = 0; bitnum <= 15; ++bitnum) {
        data = data + (digitalRead(mdio) << (15 - bitnum));
        _MIIM_DRIVER_CLOCK_PULSE();
    }

    pinMode(mdio, OUTPUT);
    pinMode(mdc, OUTPUT);
    // Reset clock and data
    digitalWrite(mdio, HIGH);
    digitalWrite(mdc, LOW);
    return data;
}

// Define MIIM bit banging functions here
void IP175::MIIM_DRIVER_WRITE(uint8_t PHY, uint8_t REG, uint16_t DATA) {
    /*
    Outputs the PHY and REG addresses to the ethernet chip. Writes 2 byte data
    packet to the ethernet chip for that PHY/REG address. Parameters: PHY
    (uint8_t):The data to be sent via the MIIM_MDIO pin values range from
    0b00000000-0b00011111 address corresponds to phy address REG (uint8_t):The
    data to be sent via the MIIM_MDIO pin values range from 0b00000000-0b00011111
                      address corresponds to reg address
           DATA (uint16_t): The data to be send to the PHY register
    Returns:
        void
    */
    // Write the preamble + start code
    _MIIM_DRIVER_START();

    // op code
    _MIIM_DRIVER_OP_CODE_WRITE();

    // write PHY and REG
    _MIIM_DRIVER_PHY_REG_OUTPUT(PHY, REG);

    // TA bits
    _MIIM_DRIVER_TA_WRITE();

    // write data
    _MIIM_DRIVER_WRITE_DATA(DATA);
}

uint16_t IP175::MIIM_DRIVER_READ(uint8_t PHY, uint8_t REG) {
    /*
    Reads 2 byte data packet from PHY and REG on ethernet chip.
    Parameters:
        PHY (uint8_t):The data to be sent via the MIIM_MDIO pin
                          values range from 0b00000000-0b00011111
                          address corresponds to phy address
        REG (uint8_t):The data to be sent via the MIIM_MDIO pin
                          values range from 0b00000000-0b00011111
                          address corresponds to reg address
     Returns:
        void
    */
    // Preamble + start code
    _MIIM_DRIVER_START();

    // op code
    _MIIM_DRIVER_OP_CODE_READ();

    // write PHY and REG
    // uint8_t PHY = 0b11111;
    // uint8_t REG = 0b11010;
    _MIIM_DRIVER_PHY_REG_OUTPUT(PHY, REG);

    // TA bits
    _MIIM_DRIVER_TA_READ();

    // read data
    uint16_t data = _MIIM_DRIVER_READ_DATA();

    return data;
}

int IP175::begin(uint32_t tick) {
    data.linked_count = 6;
    data.linked = linked;
    data.reg0_count = 6;
    data.reg0 = reg0;
    data.reg1_count = 6;
    data.reg1 = reg1;
    digitalWrite(mdc, LOW);
    digitalWrite(mdio, LOW);
    pinMode(mdc, OUTPUT);
    pinMode(mdio, OUTPUT);
    digitalWrite(mdc, LOW);
    digitalWrite(mdio, LOW);
    for (uint8_t j = 0; j <= 10; j++)
        MIIM_DRIVER_WRITE(20, 0, 0x55aa);
    //    MIIM_DRIVER_WRITE(20, 0, 0xaa55);
    //    Rtos::Delay(3000);
    uint16_t data = 0xffff;
    for (int i = 0; i < 20; i++) {
        Rtos::Delay(200);
        uint16_t d = MIIM_DRIVER_READ(20, 0);
        if (d != data) {
            core_debug("IP175Gx 20 0 :0x%x", d);
            data = d;
        }
    }
    uint16_t id = MIIM_DRIVER_READ(2, 2);
    if (id != 0x243) {
        core_debug("IP175Gx not found id=0x%x", id);
        // return 0;
    }
    uint16_t id2 = MIIM_DRIVER_READ(2, 3);
    if (id2 != 0xd80) {
        core_debug("IP175Gx not found id2=0x%x", id2);
        // return 0;
    }

    // Port mirroring settings
    // Please only configure headings marked with '***'
    // I strongly encourage that you have the data sheet for the IP175Gx to hand
    // If you spot a mistake or potential improvement (we already have lots of
    // improvements in mind) - please let us know We have currently configured the
    // default settings to mirror RX traffic on port 3 (labelled port 4 on
    // SwitchBlox) to port 2 (labelled port 3 on SwitchBlox)

    // (0) Declare and define PHY and REG variables,
    uint8_t PHY = 20;
    uint8_t REG3 = 3;
    uint8_t REG4 = 4;

    //***(1) Enable port mirroring by writing to 20.3[15] - PHY=20, REG = 3,
    //bitnum = 15
    uint8_t PORT_MIRROR_EN = 0b1;

    //***(2) Select the port that will be the mirror or 'sniffing' port
    // The SwitchBlox currently has labelled Port X+1 (i.e. Port 0 -> Labelled
    // Port 1 on SwitchBlox) ^This may change on future revisions Only select one
    // port to be the mirror port In order of 'SEL_MIRROR_PORT' declarations:
    // (default: port 4)
    //(1) Port 0
    //(2) Port 1
    //(3) Port 2
    //(4) Port 3
    //(5) Port 4 (default)

    // uint8_t SEL_MIRROR_PORT = 0b010;
    // uint8_t SEL_MIRROR_PORT = 0b011;
    uint8_t SEL_MIRROR_PORT = 0b100;
    // uint8_t SEL_MIRROR_PORT = 0b110;
    // uint8_t SEL_MIRROR_PORT = 0b111;

    //***(3) Select the port mirroring mode
    // Set the conditions that traffic on the mirrored ports must fulfill for it
    // to be mirrored to the sniffer port In order of 'PORT_MIRROR_MODE'
    // declarations: (default: mirror RX)
    //(1) mirror RX (default)
    //(2) mirror TX
    //(3) mirror RX and TX
    //(4) mirror RX or TX

    uint8_t PORT_MIRROR_MODE = 0b00;
    // uint8_t PORT_MIRROR_MODE = 0b01;
    // uint8_t PORT_MIRROR_MODE = 0b10;
    // uint8_t PORT_MIRROR_MODE = 0b11;

    //***(4a) Select the RX source port that will have its RX data flow mirrored
    //to the sniffer port
    // In order of 'SEL_RX_PORT_MIRROR' declarations: (default: None)
    //(1) port 0
    //(2) port 1
    //(3) port 2
    //(4) port 3
    //(5) port 4

    // uint8_t SEL_RX_PORT_MIRROR = 0b00000100;
    // uint8_t SEL_RX_PORT_MIRROR = 0b00001000;
    // uint8_t SEL_RX_PORT_MIRROR = 0b00010000;
    uint8_t SEL_RX_PORT_MIRROR = 0b01000000;
    // uint8_t SEL_RX_PORT_MIRROR = 0b10000000;

    //***(4b) Select the TX source port that will have its TX data flow mirrored
    //to the sniffer port
    // Must configure 'PORT_MIRROR_MODE' to include mirroring TX
    // In order of 'SEL_TX_PORT_MIRROR' declarations: (default: None)
    //(1) port 0
    //(2) port 1
    //(3) port 2
    //(4) port 3
    //(5) port 4

    // uint8_t SEL_TX_PORT_MIRROR = 0b00000100;
    // uint8_t SEL_TX_PORT_MIRROR = 0b00001000;
    // uint8_t SEL_TX_PORT_MIRROR = 0b00010000;
    // uint8_t SEL_TX_PORT_MIRROR = 0b01000000;
    uint8_t SEL_TX_PORT_MIRROR = 0b10000000;

    // (5) Create the data packet to send to 20.3/20.4 PHY.REG
    uint16_t PORT_MIRROR_CONFIG_REG3 = (((uint16_t) PORT_MIRROR_EN) << 15) |
                                       (((uint16_t) PORT_MIRROR_MODE) << 13) |
                                       (SEL_RX_PORT_MIRROR);
    uint16_t PORT_MIRROR_CONFIG_REG4 =
            (((uint16_t) SEL_MIRROR_PORT << 13) | (SEL_TX_PORT_MIRROR));

    // (6) Execute the command to write the configurations for port mirroring
    // For some reason, writing only once doesn't work all the time (if you find a
    // solution, please publish an issue to
    // https://github.com/AaronElijah/IP175GPortMirroringConfig
    for (uint8_t j = 0; j <= 10; j++) {
        //        HAL_Delay(5);
        //        MIIM_DRIVER_WRITE(PHY, REG3, PORT_MIRROR_CONFIG_REG3);
        //        HAL_Delay(5);
        //        MIIM_DRIVER_WRITE(PHY, REG4, PORT_MIRROR_CONFIG_REG4);
    }

    GPIO_SET_MDIO_MDC_MODE_INPUT();
    return 0;
}

int IP175::run(uint32_t tick) {
// #ifdef __PLATFORMIO_BUILD_DEBUG__
//     if (tick - last_tick > 200) {
//         last_tick = tick;
//         if (phy < 2)
//             phy = 2;
//         if (5 == phy)
//             phy = 6;
//         uint16_t res0 = MIIM_DRIVER_READ(phy, 0);
//         if (res0 != data.reg0[phy - 2]) {
//             core_debug("IP175Gx %d 0 %x", phy, res0);
//             data.reg0[phy - 2] = res0;
//         }
//         uint16_t res1 = MIIM_DRIVER_READ(phy, 1);
//         if (res1 != data.reg1[phy - 2]) {
//             core_debug("IP175Gx %d 1 %x", phy, res1);
//             data.reg1[phy - 2] = res1;
//         }
//         if (((res1 & (1 << 5)) != 0) && (res1 != 0xffff)) {
//             core_debug("IP175Gx %d ngo complete", phy);
//             for (uint8_t j = 0; j <= 10; j++)
//                 MIIM_DRIVER_WRITE(phy, 0, res0 & ~(1 << 12));
//         }
//         //        if ((0 == (res1 & (1 << 2))) && (0 == (res0 & (1 << 12)))) {
//         //            core_debug("IP175Gx %d link down", i);
//         //            for (uint8_t j = 0; j <= 10; j++)
//         //            MIIM_DRIVER_WRITE(i, 0, res0 | (1 << 12));
//         //        }
//         phy++;
//         if (phy > 7)
//             phy = 2;
//     }
// #endif
    return 0;
}

int IP175::diag(uint32_t tick) { return 0; }

#ifdef __PLATFORMIO_BUILD_DEBUG__

int IP175::dev_test(uint32_t tick) { return 0; }

#endif
#endif
